package com.yanboo.modules.security.security;

import com.alibaba.fastjson.JSON;
import com.querydsl.jpa.impl.JPAQuery;
import com.querydsl.jpa.impl.JPAQueryFactory;
import com.yanboo.common.annotation.AnonymousAccess;
import com.yanboo.common.annotation.CustomController;
import com.yanboo.common.base.UnifyResult;
import com.yanboo.common.utils.SecurityUtils;
import com.yanboo.common.utils.enums.RequestMethodEnum;
import com.yanboo.modules.security.config.bean.SecurityProperties;
import com.yanboo.modules.security.service.OnlineUserService;
import com.yanboo.modules.security.service.UserCacheManager;
import com.yanboo.modules.system.domain.QMenu;
import com.yanboo.modules.system.repository.MenuRepository;
import jakarta.annotation.PostConstruct;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
import org.springframework.web.util.pattern.PathPattern;

import java.util.*;

@Component
@Slf4j
public class TokenUrlInterceptor implements HandlerInterceptor {

    @Autowired
    private  TokenProvider tokenProvider;

    @Autowired
    private SecurityProperties properties;

    @Autowired
    private OnlineUserService onlineUserService;

    @Autowired
    private UserCacheManager userCacheManager;

    @Autowired
    ApplicationContext applicationContext;

    @Autowired
    JPAQueryFactory jpaQueryFactory;
    Map<String, Set<String>> generalUrls;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        UnifyResult<Object> unifyResult = UnifyResult.builder().code(401).build();

        if (handler instanceof ResourceHttpRequestHandler && resourceHandler(request,response,handler)){    //静态资源
            return true;
        }else if (handler instanceof HandlerMethod &&  handlerMethodHandler(request,response,handler)){     //动态资源
            return true;
        }
        response.setStatus(401);
        response.setContentType("application/json");
        String jsonString = JSON.toJSONString(unifyResult);
        response.setContentLength(jsonString.length());
        response.getWriter().write(jsonString);
        return false;
    }
    private boolean resourceHandler(HttpServletRequest request, HttpServletResponse response, Object handler){
        List<String> elPermissions = SecurityUtils.getCurrentUser().getAuthorities().stream().map(GrantedAuthority::getAuthority).toList();
        // 静态资源，admin或elPermissions包含当前请求的资源路径，允许访问
        return elPermissions.contains("admin") || elPermissions.contains(request.getServletPath());
    }

    private boolean handlerMethodHandler(HttpServletRequest request, HttpServletResponse response, Object handler){
        String method = request.getMethod();
        String requestURI = request.getRequestURI();
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        if (handlerMethod.hasMethodAnnotation(AnonymousAccess.class) ){             //匿名接口
            return true;
        }
        List<String> allPermissions = jpaQueryFactory.from(QMenu.menu).select(QMenu.menu.permission).where(QMenu.menu.permission.isNotEmpty()).distinct().fetch();
        // 未建菜单的请求允许已登录用户访问
        if (Objects.nonNull(handlerMethod.getBean().getClass().getAnnotation(CustomController.class)) && generalUrls.containsKey(method) && generalUrls.get(method).contains(requestURI) && !allPermissions.contains(method+","+requestURI) && Objects.nonNull(SecurityUtils.getCurrentUser())){ //
            return true;
        }

        // 精确权限
        // 获取当前用户的所有权限
        List<String> elPermissions = SecurityUtils.getCurrentUser().getAuthorities().stream().map(GrantedAuthority::getAuthority).toList();
        // 判断当前用户的所有权限是否包含接口上定义的权限
        return elPermissions.contains("admin")   || elPermissions.stream().anyMatch(permission -> permission.equalsIgnoreCase(method + "," + requestURI));
    }


    @PostConstruct
    public void init(){
        RequestMappingHandlerMapping requestMappingHandlerMapping = (RequestMappingHandlerMapping) applicationContext.getBean("requestMappingHandlerMapping");
        Map<RequestMappingInfo, HandlerMethod> handlerMethodMap = requestMappingHandlerMapping.getHandlerMethods();
        generalUrls = getGeneralUrl(handlerMethodMap);
    }

    private Map<String, Set<String>> getGeneralUrl(Map<RequestMappingInfo, HandlerMethod> handlerMethodMap) {
        Map<String, Set<String>> generalUrls=new HashMap<>(8);
        Set<String> get = new HashSet<>();
        Set<String> post = new HashSet<>();
        Set<String> put = new HashSet<>();
        Set<String> patch = new HashSet<>();
        Set<String> delete = new HashSet<>();
        Set<String> all = new HashSet<>();
        for (Map.Entry<RequestMappingInfo, HandlerMethod> infoEntry : handlerMethodMap.entrySet()) {
            HandlerMethod handlerMethod = infoEntry.getValue();
            if (!handlerMethod.hasMethodAnnotation(AnonymousAccess.class)) {
                List<RequestMethod> requestMethods = new ArrayList<>(infoEntry.getKey().getMethodsCondition().getMethods());
                RequestMethodEnum request = RequestMethodEnum.find(requestMethods.isEmpty() ? RequestMethodEnum.ALL.getType() : requestMethods.get(0).name());
                switch (Objects.requireNonNull(request)) {
                    case GET:
                        assert infoEntry.getKey().getPathPatternsCondition() != null;
                        get.addAll(infoEntry.getKey().getPathPatternsCondition().getPatterns().stream().map(PathPattern::getPatternString).toList());
                        break;
                    case POST:
                        assert infoEntry.getKey().getPathPatternsCondition() != null;
                        post.addAll(infoEntry.getKey().getPathPatternsCondition().getPatterns().stream().map(PathPattern::getPatternString).toList());
                        break;
                    case PUT:
                        assert infoEntry.getKey().getPathPatternsCondition() != null;
                        put.addAll(infoEntry.getKey().getPathPatternsCondition().getPatterns().stream().map(PathPattern::getPatternString).toList());
                        break;
                    case PATCH:
                        assert infoEntry.getKey().getPathPatternsCondition() != null;
                        patch.addAll(infoEntry.getKey().getPathPatternsCondition().getPatterns().stream().map(PathPattern::getPatternString).toList());
                        break;
                    case DELETE:
                        assert infoEntry.getKey().getPathPatternsCondition() != null;
                        delete.addAll(infoEntry.getKey().getPathPatternsCondition().getPatterns().stream().map(PathPattern::getPatternString).toList());
                        break;
                    default:
                        assert infoEntry.getKey().getPathPatternsCondition() != null;
                        all.addAll(infoEntry.getKey().getPathPatternsCondition().getPatterns().stream().map(PathPattern::getPatternString).toList());
                        break;
                }
            }
        }
        generalUrls.put(RequestMethodEnum.GET.getType(), get);
        generalUrls.put(RequestMethodEnum.POST.getType(), post);
        generalUrls.put(RequestMethodEnum.PUT.getType(), put);
        generalUrls.put(RequestMethodEnum.PATCH.getType(), patch);
        generalUrls.put(RequestMethodEnum.DELETE.getType(), delete);
        generalUrls.put(RequestMethodEnum.ALL.getType(), all);
        return generalUrls;
    }
}
